import com.github.javaparser.ast.*;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.comments.BlockComment;
import com.github.javaparser.ast.comments.JavadocComment;
import com.github.javaparser.ast.comments.LineComment;
import com.github.javaparser.ast.expr.*;
import com.github.javaparser.ast.modules.*;
import com.github.javaparser.ast.stmt.*;
import com.github.javaparser.ast.type.*;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;

class Visitor extends VoidVisitorAdapter<TextWriter> {

    private boolean declarationOnly = false;
    private final String AbstractKeyword = "抽象";
    private final String FinalKeyword = "终态";
    private final String AsKeyword = "为";
    private final String BooleanKeyword = "布尔";
    private final String BreakKeyword = "跳出";
    private final String CaseKeyword = "分支";
    private final String CatchKeyword = "捕获";
    private final String ClassKeyword = "类";
    private final String ContinueKeyword = "继续";
    private final String ConstKeyword = "常量";
    private final String ConstructorKeyword = "构造";
    private final String DefaultKeyword = "默认";
    private final String DoKeyword = "执行";
    private final String ElseKeyword = "否则";
    private final String EnumKeyword = "枚举";
    private final String ExtendsKeyword = "继承";
    private final String FalseKeyword = "假";
    private final String FinallyKeyword = "最后";
    private final String ForKeyword = "遍历";
    private final String FunctionKeyword = "方法";
    private final String IfKeyword = "如果";
    private final String ImplementsKeyword = "实现";
    private final String ImportKeyword = "导入";
    private final String OfKeyword = "于";
    private final String InstanceOfKeyword = "实例于";
    private final String InterfaceKeyword = "接口";
    private final String AnnotationKeyword = "注解";
    private final String NewKeyword = "创建";
    private final String NullKeyword = "空";
    private final String ShortKeyword = "短整数";
    private final String IntKeyword = "整数";
    private final String LongKeyword = "长整数";
    private final String ByteKeyword = "字节";
    private final String CharKeyword = "字符";
    private final String FloatKeyword = "小数";
    private final String DoubleKeyword = "长小数";
    private final String PackageKeyword = "包";
    private final String PrivateKeyword = "私有";
    private final String ProtectedKeyword = "保护";
    private final String PublicKeyword = "公有";
    private final String ReturnKeyword = "返回";
    private final String StaticKeyword = "静态";
    private final String SuperKeyword = "基类";
    private final String SwitchKeyword = "选择";
    private final String ThisKeyword = "本对象";
    private final String ThrowKeyword = "抛";
    private final String ThrowsKeyword = "异常";
    private final String TrueKeyword = "真";
    private final String TryKeyword = "尝试";
    private final String VarKeyword = "变量";
    private final String VoidKeyword = "无";
    private final String WhileKeyword = "循环";
    private final String Transient  = "瞬态";
    private final String Volatile = "易变";
    private final String Synchronized = "同步";
    private final String Native = "本地";
    private final String Strictfp = "严格";
    private final String Transitive = "传递";

    Visitor(boolean declarationOnly) {
        this.declarationOnly = declarationOnly;
    }

    // ANNOTATION
    @Override
    public void visit(final Modifier n, TextWriter writer) {
        writeModifier(n, writer);
    }

    // COMMENT
    @Override
    public void visit(final BlockComment n, TextWriter writer) {
        if (n != null && n.getParentNode().isPresent()) {
            String text = n.toString();
            text = text.replace("\r\n", "\n");
            String[] arr = text.split("\n");
            for (String s : arr) {
                writer.writeString(s);
                writer.writeLine();
            }
        }
    }

    @Override
    public void visit(final JavadocComment n, TextWriter writer) {
        if (n != null) {
            String text = n.toString();
            text = text.replace("\r\n", "\n");
            String[] arr = text.split("\n");
            for (String s : arr) {
                writer.writeString(s);
                writer.writeLine();
            }
        }
    }

    @Override
    public void visit(final LineComment n, TextWriter writer) {
        writer.writeString(n.toString());
    }

    // EXPRESSION
    public void visit(final FieldAccessExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getScope().accept(this, writer);
        writer.writeString(".");
        n.getName().accept(this, writer);

//        // ???
//        n.getTypeArguments().ifPresent((l) -> {
//            l.forEach((v) -> {
//                v.accept(this, writer);
//            });
//        });

    }

    @Override
    public void visit(final CastExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getExpression().accept(this, writer);
        writer.writeString(" " + AsKeyword + " ");
        n.getType().accept(this, writer);
    }

    @Override
    public void visit(final CatchClause n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeString(CatchKeyword);
        writer.writeString("(");
        n.getParameter().accept(this, writer);
        writer.writeString(")");
        n.getBody().accept(this, writer);
    }

    @Override
    public void visit(final ClassExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getType().accept(this, writer);
        writer.writeString(".");
        writer.writeString(ClassKeyword);
    }

    @Override
    public void visit(final MarkerAnnotationExpr n, TextWriter writer) {
        n.getComment().ifPresent(l -> {
            l.accept(this, writer);
        });
        writer.writeLine();
        writer.writeString("@");
        writer.writeString(n.getName().toString());
        writer.writeLine();
    }

    @Override
    public void visit(final NormalAnnotationExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeLine();
        writer.writeString("@");
        n.getName().accept(this, writer);
        writer.writeString("(");
        n.getPairs().forEach((p) -> {
            p.accept(this, writer);
            writer.writeString(", ");
        });
        writer.removeTrailing(", ");
        writer.writeString(")");
    }

    @Override
    public void visit(final SingleMemberAnnotationExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeLine();
        writer.writeString("@");
        n.getName().accept(this, writer);
        writer.writeString("(");
        n.getMemberValue().accept(this, writer);
        writer.writeString(")");
    }

    @Override
    public void visit(final ArrayAccessExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getName().accept(this, writer);
        writer.writeString("[");
        n.getIndex().accept(this, writer);
        writer.writeString("]");
    }

    @Override
    public void visit(final ArrayCreationExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeString(NewKeyword);
        writer.writeString(" ");

        n.getElementType().accept(this, writer);
        n.getLevels().forEach((p) -> {
            p.accept(this, writer);
        });
        n.getInitializer().ifPresent((l) -> {
            l.accept(this, writer);
        });


    }

    @Override
    public void visit(final ArrayInitializerExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeString("{");
        NodeList<Expression> list =  n.getValues();
        for (int i = 0; i < list.size(); i++) {
            if (i > 0) writer.writeString(", ");
            list.get(i).accept(this, writer);
        }
        writer.writeString("}");

    }

    @Override
    public void visit(final AssignExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getTarget().accept(this, writer);
        writer.writeString(" = ");
        n.getValue().accept(this, writer);
    }

    @Override
    public void visit(final BinaryExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getLeft().accept(this, writer);
        writer.writeString(" " + n.getOperator().asString() + " ");
        n.getRight().accept(this, writer);
    }

    @Override
    public void visit(final BooleanLiteralExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });

        if (n.getValue()){
            writer.writeString(TrueKeyword);
        } else {
            writer.writeString(FalseKeyword);
        }
    }

    @Override
    public void visit(final CharLiteralExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeString(n.toString());
    }

    @Override
    public void visit(final ConditionalExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getCondition().accept(this, writer);
        writer.writeString(" ? ");
        n.getElseExpr().accept(this, writer);
        writer.writeString(" : ");
        n.getThenExpr().accept(this, writer);
    }

    @Override
    public void visit(final DoubleLiteralExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeString(n.getValue());
    }

    @Override
    public void visit(final EnclosedExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeString("(");
        n.getInner().accept(this, writer);
        writer.writeString(")");
    }

    @Override
    public void visit(final InstanceOfExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getExpression().accept(this, writer);
        writer.writeString(" ");
        writer.writeString(InstanceOfKeyword);
        writer.writeString(" ");
//        // Java 8 not support, ignore it.
//        n.getPattern().ifPresent((l) -> {
//            l.accept(this, writer);
//        });
        n.getType().accept(this, writer);
    }

    @Override
    public void visit(final IntegerLiteralExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeString(n.getValue());
    }

    @Override
    public void visit(final LongLiteralExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeString(n.getValue());
    }

    @Override
    public void visit(final MemberValuePair n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getName().accept(this, writer);
        writer.writeString(" = ");
        n.getValue().accept(this, writer);
    }

    @Override
    public void visit(final MethodCallExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getScope().ifPresent((l) -> {
            l.accept(this, writer);
            writer.writeString(".");
        });

        n.getName().accept(this, writer);
        n.getTypeArguments().ifPresent((l) -> {
            writer.writeString("<");
            l.forEach((v) -> {
                v.accept(this, writer);
                writer.writeString(", ");
            });
            writer.removeTrailing(", ");
            writer.writeString(">");
        });
        writer.writeString("(");
        n.getArguments().forEach((p) -> {
            p.accept(this, writer);
            writer.writeString(", ");
        });
        writer.removeTrailing(", ");
        writer.writeString(")");
    }

    @Override
    public void visit(final Name n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getQualifier().ifPresent((l) -> {
            l.accept(this, writer);
            writer.writeString(".");
        });
        writer.writeString(n.getIdentifier());

        // writer.writeString(n.asString());
    }

    @Override
    // A name that consists of a single identifier. In other words: it.does.NOT.contain.dots.
    public void visit(final SimpleName n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeString(n.getIdentifier());
    }

    @Override
    public void visit(final NameExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getName().accept(this, writer);
    }

    @Override
    public void visit(final NullLiteralExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeString(NullKeyword);
    }

    @Override
    public void visit(final ObjectCreationExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });

        n.getScope().ifPresent((l) -> {
            l.accept(this, writer);
            writer.writeString(".");
        });
        writer.writeString(NewKeyword);
        writer.writeString(" ");

        n.getType().accept(this, writer);
        n.getTypeArguments().ifPresent((l) -> {
            writer.writeString("<");
            l.forEach((v) -> {
                v.accept(this, writer);
                writer.writeString(", ");
            });
            writer.removeTrailing(", ");
            writer.writeString(">");
        });
        writer.writeString("(");
        n.getArguments().forEach((p) -> {
            p.accept(this, writer);
            writer.writeString(", ");
        });
        writer.removeTrailing(", ");
        writer.writeString(")");
        n.getAnonymousClassBody().ifPresent((l) -> {
            writer.writeString("{");
            writer.writeLine();
            writer.increaseIndent();
            l.forEach((v) -> {
                writer.writeLine();
                writer.writeEmptyLine();
                v.accept(this, writer);
            });
            writer.decreaseIndent();
            writer.writeLine();
            writer.writeString("}");
        });

    }

    @Override
    public void visit(final StringLiteralExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeString("\"");
        writer.writeString(n.getValue());
        writer.writeString("\"");
    }

    @Override
    public void visit(final SuperExpr n, TextWriter writer) {
        n.getTypeName().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
    }

    @Override
    public void visit(final ThisExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getTypeName().ifPresent((l) -> {
            l.accept(this, writer);
        });

    }

    @Override
    public void visit(final UnaryExpr n, TextWriter writer) {

        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });

        if (n.isPrefix()) {
            writer.writeString(n.getOperator().asString());
        }
        n.getExpression().accept(this, writer);
        if (n.isPostfix()) {
            writer.writeString(n.getOperator().asString());
        }
    }

    @Override
    public void visit(final LambdaExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeString("(");
        n.getParameters().forEach((p) -> {
            p.accept(this, writer);
            writer.writeString(", ");
        });
        writer.removeTrailing(", ");
        writer.writeString(")");
        writer.writeString(" -> ");

        n.getBody().accept(this, writer);
    }

    @Override
    public void visit(final MethodReferenceExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getTypeArguments().ifPresent((l) -> {
            l.forEach((v) -> {
                v.accept(this, writer);
            });
        });
        n.getScope().accept(this, writer);
    }

    @Override
    public void visit(final TypeExpr n, TextWriter writer) {
        writer.writeString("TypeExpr???");
    }

    @Override
    public void visit(final TextBlockLiteralExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeString(n.getValue());
    }

    public void visit(final SwitchExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeString(SwitchKeyword);
        writer.writeString("(");
        n.getSelector().accept(this, writer);
        writer.writeString(")");

        writer.writeString("{");
        writer.increaseIndent();
        writer.writeLine();
        n.getEntries().forEach((p) -> {
            p.accept(this, writer);
            writer.writeLine();
        });
        writer.decreaseIndent();
        writer.writeString("}");
    }

    @Override
    public void visit(final PatternExpr n, TextWriter writer) {
        // This is java14 support, ignore it.
    }

    // TYPE
    public void visit(final UnknownType n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getAnnotations().forEach((p) -> {
            p.accept(this, writer);
            writer.writeLine();
        });
        writer.writeString("Unknown");
    }

    private void writePrimitiveType(PrimitiveType n, TextWriter writer) {
        PrimitiveType.Primitive t = n.getType();
        if (t == PrimitiveType.Primitive.BOOLEAN) {
            writer.writeString(BooleanKeyword);
        } else if (t == PrimitiveType.Primitive.BYTE) {
            writer.writeString(ByteKeyword);
        } else if (t == PrimitiveType.Primitive.CHAR) {
            writer.writeString(CharKeyword);
        } else if (t == PrimitiveType.Primitive.SHORT) {
            writer.writeString(ShortKeyword);
        } else if (t == PrimitiveType.Primitive.INT) {
            writer.writeString(IntKeyword);
        } else if (t == PrimitiveType.Primitive.LONG) {
            writer.writeString(LongKeyword);
        } else if (t == PrimitiveType.Primitive.FLOAT) {
            writer.writeString(FloatKeyword);
        } else if (t == PrimitiveType.Primitive.DOUBLE) {
            writer.writeString(DoubleKeyword);
        }
    }

    @Override
    public void visit(final PrimitiveType n, TextWriter writer) {
        n.getAnnotations().forEach(a -> {
            a.accept(this, writer);
            writer.writeLine();
        });
        writePrimitiveType(n, writer);
        n.getComment().ifPresent(l -> {
            writer.writeString(" ");
            l.accept(this, writer);
        });
    }

    @Override
    public void visit(final ArrayType n, TextWriter writer) {
        n.getComment().ifPresent(l -> {
            l.accept(this, writer);
        });
        n.getAnnotations().forEach(a -> {
            a.accept(this, writer);
            writer.writeLine();
        });
        n.getComponentType().accept(this, writer);
        writer.writeString("[]");
    }

    public void visit(final ArrayCreationLevel n, TextWriter writer) {
        n.getAnnotations().forEach((p) -> {
            p.accept(this, writer);
            writer.writeLine();
        });
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });

        writer.writeString("[");
        n.getDimension().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeString("]");
    }

    @Override
    public void visit(final IntersectionType n, TextWriter writer){
        n.getComment().ifPresent(l -> {
            l.accept(this, writer);
        });
        n.getAnnotations().forEach(a -> {
            a.accept(this, writer);
            writer.writeLine();
        });
        writer.writeString(n.toString());
    }

    @Override
    public void visit(final UnionType n, TextWriter writer) {
        n.getComment().ifPresent(l -> {
            l.accept(this, writer);
        });
        n.getAnnotations().forEach(a -> {
            a.accept(this, writer);
            writer.writeLine();
        });
        writer.writeString(n.toString());
    }

    @Override
    public void visit(final VoidType n, TextWriter writer) {
        n.getComment().ifPresent(l -> {
            l.accept(this, writer);
        });
        n.getAnnotations().forEach(a -> {
            a.accept(this, writer);
            writer.writeLine();
        });
        writer.writeString(VoidKeyword);
    }

    @Override
    public void visit(final WildcardType n, TextWriter writer) {
        n.getComment().ifPresent(l -> {
            l.accept(this, writer);
        });
        n.getAnnotations().forEach(a -> {
            a.accept(this, writer);
            writer.writeLine();
        });

        writer.writeString("?");
        n.getExtendedType().ifPresent((t)-> {
            writer.writeString(" ");
            writer.writeString(ExtendsKeyword);
            writer.writeString(" ");

            t.accept(this, writer);
        });
        n.getSuperType().ifPresent(t -> {
            writer.writeString(" ");
            writer.writeString(SuperKeyword);
            writer.writeString(" ");

            t.accept(this, writer);
        });
    }

    @Override
    public void visit(final VarType n, TextWriter writer) {
        n.getComment().ifPresent(l -> {
            l.accept(this, writer);
        });
        n.getAnnotations().forEach(a -> {
            a.accept(this, writer);
            writer.writeLine();
        });
        writer.writeString(n.toString());
    }

    // DECLARATION
    @Override
    public void visit(final VariableDeclarationExpr n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getAnnotations().forEach((p) -> {
            p.accept(this, writer);
            writer.writeLine();
        });
        n.getModifiers().forEach(m -> {
            m.accept(this, writer);
            writer.writeString(" ");
        });

        writer.writeString(VarKeyword);
        writer.writeString(" ");
        n.getVariables().forEach((p) -> {
            p.accept(this, writer);
            writer.writeString(", ");
        });
        writer.removeTrailing(", ");
    }

    @Override
    public void visit(final VariableDeclarator n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });

        n.getName().accept(this, writer);
        writer.writeString(":");
        writer.writeString(" ");
        n.getType().accept(this, writer);
        n.getInitializer().ifPresent((l) -> {
            writer.writeString(" ");
            writer.writeString("=");
            writer.writeString(" ");
            l.accept(this, writer);
        });

    }

    public void visit(final ImportDeclaration n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });

        writer.writeString(ImportKeyword);
        writer.writeString(" ");
        n.getName().accept(this, writer);
        if (n.isAsterisk()) {
            writer.writeString(".");
            writer.writeString("*");
        }
        writer.writeLine();
    }

    // Java 8 not support, so ignore it.
    public void visit(final ModuleDeclaration n, TextWriter writer) { }

    // Java 8 not support, so ignore it.
    public void visit(final ModuleRequiresDirective n, TextWriter writer) { }

    // Java 8 not support, so ignore it.
    public void visit(final ModuleExportsDirective n, TextWriter writer) { }

    // Java 8 not support, so ignore it.
    public void visit(final ModuleProvidesDirective n, TextWriter writer) { }

    // Java 8 not support, so ignore it.
    public void visit(final ModuleUsesDirective n, TextWriter writer) { }

    // Java 8 not support, so ignore it.
    public void visit(final ModuleOpensDirective n, TextWriter writer) { }

    public void visit(final PackageDeclaration n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getAnnotations().forEach((p) -> {
            p.accept(this, writer);
            writer.writeLine();
        });
        writer.writeString(PackageKeyword);
        writer.writeString(" ");
        n.getName().accept(this, writer);
        writer.writeLine();
        writer.writeEmptyLine();
    }

    @Override
    public void visit(final CompilationUnit n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getPackageDeclaration().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getImports().forEach((p) -> {
            p.accept(this, writer);
        });
        writer.writeEmptyLine();

//            Java8 Not support ignore it.
//            n.getModule().ifPresent((l) -> {
//                l.accept(this, arg);
//            });

        n.getTypes().forEach((p) -> {
            p.accept(this, writer);
        });
        ;
    }

    @Override
    public void visit(final ClassOrInterfaceType n, TextWriter writer) {
        n.getAnnotations().forEach(a -> {
            a.accept(this, writer);
            writer.writeLine();
        });
        n.getScope().ifPresent(s->{
            s.accept(this, writer);
            writer.writeString(".");
        });
        n.getName().accept(this, writer);
        n.getTypeArguments().ifPresent(args->{
            writer.writeString("<");
            args.forEach(a->{
                a.accept(this, writer);
                writer.writeString(", ");
            });
            writer.removeTrailing(", ");
            writer.writeString(">");
        });
        n.getComment().ifPresent(l -> {
            writer.writeString(" ");
            l.accept(this, writer);
        });
    }

    @Override
    public void visit(final Parameter n, TextWriter writer) {
        n.getAnnotations().forEach(a -> {
            a.accept(this, writer);
            writer.writeLine();
        });
        n.getModifiers().forEach(m -> {
            m.accept(this, writer);
            writer.writeString(" ");
        });

        writer.writeString(n.getName().toString());
        writer.writeString(":");
        writer.writeString(" ");
        n.getType().accept(this, writer);

        n.getComment().ifPresent(l -> {
            // Java 8 standard special
            if (l.toString().equals("// throws Exception\r\n")) {
                return;
            }

            l.accept(this, writer);
        });
    }

    @Override
    public void visit(final TypeParameter n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getAnnotations().forEach((p) -> {
            p.accept(this, writer);
        });
        n.getName().accept(this, writer);


        if (!n.getTypeBound().isEmpty()) {
            writer.writeString(" ");
            writer.writeString(ExtendsKeyword);
            writer.writeString(" ");
            n.getTypeBound().forEach((p) -> {
                p.accept(this, writer);
                writer.writeString("&");
            });
            writer.removeTrailing("&");
        }
    }

    private void writeTypeParameters(NodeList<TypeParameter> typeParameters, TextWriter writer) {
        if (!typeParameters.isEmpty()) {
            writer.writeString("<");
            typeParameters.forEach(t->{
                t.accept(this, writer);
                writer.writeString(", ");
            });
            writer.removeTrailing(", ");
            writer.writeString(">");
        }
    }

    @Override
    public void visit(final ConstructorDeclaration n, TextWriter writer) {
        n.getComment().ifPresent(l -> {
            l.accept(this, writer);
        });
        n.getAnnotations().forEach(a -> {
            a.accept(this, writer);
            writer.writeLine();
        });
        n.getModifiers().forEach(m -> {
            m.accept(this, writer);
            writer.writeString(" ");
        });

        writer.writeString(ConstructorKeyword);
        writeTypeParameters(n.getTypeParameters(), writer);


        writer.writeString("(");
        NodeList<Parameter> parameters = n.getParameters();
        for (int i = 0; i < parameters.size(); i ++ ) {
            if (i > 0) {
                writer.writeString(", ");
            }
            parameters.get(i).accept(this, writer);
        }
        writer.writeString(") ");

        if (!n.getThrownExceptions().isEmpty()) {
            writer.writeString(ThrowsKeyword);
            writer.writeString(" ");

            n.getThrownExceptions().forEach( e-> {
                e.accept(this, writer);
                writer.writeString(", ");
            });
            writer.removeTrailing(", ");
        }

        writer.writeLine();
        writer.writeLine();
    }

    @Override
    public void visit(MethodDeclaration md, TextWriter writer) {
        md.getComment().ifPresent(l -> {
            l.accept(this, writer);
        });
        md.getAnnotations().forEach(a -> {
            a.accept(this, writer);
            writer.writeLine();
        });
        md.getModifiers().forEach(m -> {
            m.accept(this, writer);
            writer.writeString(" ");
        });

        writer.writeString(FunctionKeyword);
        writer.writeString(" ");
        writer.writeString(md.getName().toString());

        writeTypeParameters(md.getTypeParameters(), writer);

        writer.writeString("(");
        md.getParameters().forEach(p -> {
            p.accept(this, writer);
            writer.writeString(", ");
        });
        writer.removeTrailing(", ");
        writer.writeString(")");
        if (!md.getType().isVoidType()){
            writer.writeString(": ");
            md.getType().accept(this, writer);
        }


        writer.writeString(" ");
        if (!md.getThrownExceptions().isEmpty()) {
            writer.writeString(ThrowsKeyword);
            writer.writeString(" ");

            md.getThrownExceptions().forEach(t->{
                t.accept(this, writer);
                writer.writeString(", ");
            });
            writer.removeTrailing(", ");
            writer.writeString(" ");
        }

        if (!declarationOnly) {
            md.getBody().ifPresent((b)->{
                b.accept(this, writer);
            });
        }

        writer.writeLine();
    }

    private boolean isPublic(NodeList<Modifier> list) {
        if (list.isEmpty()) {
            return false;
        }

        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).getKeyword() == Modifier.Keyword.PUBLIC) {
                return true;
            }
        }

        return false;
    }

    @Override
    public void visit(final FieldDeclaration n, TextWriter writer) {
        n.getVariables().forEach((p) -> {
            n.getComment().ifPresent(l -> {
                l.accept(this, writer);
            });
            n.getAnnotations().forEach(a -> {
                a.accept(this, writer);
                writer.writeLine();
            });
            n.getModifiers().forEach(m -> {
                m.accept(this, writer);
                writer.writeString(" ");
            });


            writer.writeString(VarKeyword);
            writer.writeString(" ");
            writer.writeString(p.getName().toString());
            writer.writeString(":");
            writer.writeString(" ");
            p.getType().accept(this, writer);

            p.getInitializer().ifPresent(init->{

                if (declarationOnly) {

                    // If current node not public, ignore it.
                    if (!isPublic(n.getModifiers())) {
                        return;
                    }

                    boolean isFinal = false;

                    if (n.getModifiers() != null) {


                        for (int i = 0; i < n.getModifiers().size(); i ++) {
                            if (n.getModifiers().get(i).getKeyword() == Modifier.Keyword.FINAL) {
                                isFinal = true;
                                break;
                            }
                        }
                    }

                    if (!isFinal) {
                        return;
                    }
                }

                writer.writeString(" = ");
                init.accept(this, writer);
            });
            writer.writeLine();
        });
    }

    private boolean insideObject(Node node) {
        if (node.getParentNode().isPresent()) {
            Node parent = node.getParentNode().get();
            return parent instanceof ClassOrInterfaceDeclaration ||
                    parent instanceof EnumDeclaration ||
                    parent instanceof AnnotationDeclaration ||
                    parent instanceof ClassExpr;
        }
        return false;
    }

    private boolean isAccessibilityModifier(Modifier modifier) {
        return modifier.getKeyword() == Modifier.Keyword.PUBLIC ||
                modifier.getKeyword() == Modifier.Keyword.PRIVATE ||
                modifier.getKeyword() == Modifier.Keyword.PROTECTED;
    }

//    private void writeModifiers (Node node, NodeList<Modifier> modifiers, TextWriter writer) {
//
//        if (modifiers == null || modifiers.size() == 0) {
//            return;
//        }
//
//        boolean ignoreFirst = false;
//        boolean appendSamePackage = false;
//
//        if (modifiers.getParentNode().isPresent() && insideObject(node)) {
//            if (isAccessibilityModifier(modifiers.get(0))) {
//                if (modifiers.get(0).getKeyword() == Modifier.Keyword.PUBLIC) {
//                    ignoreFirst = true;
//                }
//            } else {
//                appendSamePackage = true;
//            }
//        }
//
//        if (appendSamePackage) {
//            writer.writeString(SamePackageKeyword);
//            writer.writeString(" ");
//        }
//
//        for (int i = 0; i < modifiers.size(); i ++ ) {
//            if (i == 0 && ignoreFirst) {
//                continue;
//            }
//
//            modifiers.get(i).accept(this, writer);
//            writer.writeString(" ");
//        }
//    }

    private void writeModifier(Modifier modifier, TextWriter writer) {
        if (modifier.getKeyword() == Modifier.Keyword.DEFAULT) {
            writer.writeString(DefaultKeyword);
        } else if (modifier.getKeyword() == Modifier.Keyword.PUBLIC) {
            writer.writeString(PublicKeyword);
        } else if (modifier.getKeyword() == Modifier.Keyword.PROTECTED) {
            writer.writeString(ProtectedKeyword);
        } else if (modifier.getKeyword() == Modifier.Keyword.PRIVATE) {
            writer.writeString(PrivateKeyword);
        } else if (modifier.getKeyword() == Modifier.Keyword.ABSTRACT) {
            writer.writeString(AbstractKeyword);
        } else if (modifier.getKeyword() == Modifier.Keyword.STATIC) {
            writer.writeString(StaticKeyword);
        } else if (modifier.getKeyword() == Modifier.Keyword.FINAL) {
            writer.writeString(FinalKeyword);
        } else if (modifier.getKeyword() == Modifier.Keyword.TRANSIENT) {
            writer.writeString(Transient);
        } else if (modifier.getKeyword() == Modifier.Keyword.VOLATILE) {
            writer.writeString(Volatile);
        } else if (modifier.getKeyword() == Modifier.Keyword.SYNCHRONIZED) {
            writer.writeString(Synchronized);
        } else if (modifier.getKeyword() == Modifier.Keyword.NATIVE) {
            writer.writeString(Native);
        } else if (modifier.getKeyword() == Modifier.Keyword.STRICTFP) {
            writer.writeString(Strictfp);
        } else if (modifier.getKeyword() == Modifier.Keyword.TRANSITIVE) {
            writer.writeString(Transitive);
        } else {
            writer.writeString("UnknownModifier ");
        }
    }

    public void visit(final InitializerDeclaration n, TextWriter writer) {
//         ignore it
//        n.getComment().ifPresent((l) -> {
//            l.accept(this, writer);
//        });
//        n.getAnnotations().forEach((p) -> {
//            p.accept(this, writer);
//            writer.writeLine();
//        });
//
//        writer.writeString(StaticKeyword);
//        writer.writeString(" ");
//        n.getBody().accept(this, writer);
    }

    @Override
    public void visit(final ClassOrInterfaceDeclaration n, TextWriter writer) {
        n.getComment().ifPresent(l -> {
            l.accept(this, writer);
        });
        n.getAnnotations().forEach(a -> {
            a.accept(this, writer);
            writer.writeLine();
        });
        n.getModifiers().forEach(m -> {
            m.accept(this, writer);
            writer.writeString(" ");
        });

        if (n.isInterface()){
            writer.writeString(InterfaceKeyword);
        } else {
            writer.writeString(ClassKeyword);
        }
        writer.writeString(" ");
        writer.writeString(n.getName().toString());
        writeTypeParameters(n.getTypeParameters(), writer);
        writer.writeString(" ");

        if (!n.getExtendedTypes().isEmpty()) {
            writer.writeString(ExtendsKeyword);
            writer.writeString(" ");
            n.getExtendedTypes().forEach(e->{
                e.accept(this, writer);
                writer.writeString(", ");
            });
            writer.removeTrailing(", ");
            writer.writeString(" ");
        }

        if (!n.getImplementedTypes().isEmpty()) {
            writer.writeString(ImplementsKeyword);
            writer.writeString(" ");

            n.getImplementedTypes().forEach(i -> {
                i.accept(this, writer);
                writer.writeString(", ");
            });
            writer.removeTrailing(", ");
            writer.writeString(" ");
        }

        writer.writeString("{");
        writer.writeLine();
        writer.increaseIndent();
        n.getMembers().forEach((p) -> {
            writer.writeEmptyLine();
            p.accept(this, writer);
        });
        writer.decreaseIndent();
        writer.writeLine();
        writer.writeString("}");
        writer.writeLine();
    }

    @Override
    public void visit(final AnnotationMemberDeclaration n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getAnnotations().forEach((p) -> {
            p.accept(this, writer);
            writer.writeLine();
        });
        n.getModifiers().forEach(m -> {
            m.accept(this, writer);
            writer.writeString(" ");
        });

        writer.writeString(FunctionKeyword);
        writer.writeString(" ");
        n.getName().accept(this, writer);
        writer.writeString("(): ");
        n.getType().accept(this, writer);
        n.getDefaultValue().ifPresent((l) -> {
            writer.writeString(" ");
            writer.writeString(DefaultKeyword);
            writer.writeString(" ");

            l.accept(this, writer);
        });
    }

    @Override
    public void visit(final AnnotationDeclaration n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getAnnotations().forEach((p) -> {
            p.accept(this, writer);
            writer.writeLine();
        });
        n.getModifiers().forEach(m -> {
            m.accept(this, writer);
            writer.writeString(" ");
        });

        writer.writeString(AnnotationKeyword);
        writer.writeString(" ");
        n.getName().accept(this, writer);
        writer.writeString(" ");
        writer.writeString("{");
        writer.increaseIndent();
        n.getMembers().forEach((p) -> {
            writer.writeEmptyLine();
            p.accept(this, writer);
        });
        writer.decreaseIndent();
        writer.writeLine();
        writer.writeString("}");
        writer.writeLine();
    }

    public void visit(final EnumConstantDeclaration n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getAnnotations().forEach((p) -> {
            p.accept(this, writer);
            writer.writeLine();
        });
        n.getName().accept(this, writer);

        writer.writeString("(");
        n.getArguments().forEach((p) -> {
            p.accept(this, writer);
            writer.writeString(", ");
        });
        writer.removeTrailing(", ");
        writer.writeString(")");

        if (!n.getClassBody().isEmpty()) {
            writer.writeString(" ");
            writer.writeString("{");
            writer.writeLine();
            writer.increaseIndent();
            n.getClassBody().forEach((p) -> {
                p.accept(this, writer);
            });
            writer.decreaseIndent();
            writer.writeLine();
            writer.writeString("}");
        }
    }

    @Override
    public void visit(final EnumDeclaration n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getAnnotations().forEach((p) -> {
            p.accept(this, writer);
            writer.writeLine();
        });
        n.getModifiers().forEach(m -> {
            m.accept(this, writer);
            writer.writeString(" ");
        });

        writer.writeString(EnumKeyword);
        writer.writeString(" ");
        n.getName().accept(this, writer);
        writer.writeString(" ");

        if (!n.getImplementedTypes().isEmpty()){
            writer.writeString(ImplementsKeyword);
            writer.writeString(" ");
            n.getImplementedTypes().forEach((p) -> {
                p.accept(this, writer);
                writer.writeString(", ");
            });
            writer.removeTrailing(", ");
            writer.writeString(" ");
        }

        writer.writeString("{");
        writer.writeLine();
        writer.increaseIndent();

        n.getEntries().forEach((p) -> {
            writer.writeEmptyLine();
            p.accept(this, writer);
            writer.writeString(", ");
        });
        writer.removeTrailing(", ");
        writer.writeString(";");
        writer.writeLine();


        n.getMembers().forEach((p) -> {
            writer.writeEmptyLine();
            p.accept(this, writer);
        });

        writer.decreaseIndent();
        writer.writeLine();
        writer.writeString("}");
    }

    // STATEMENT

    private void encircleStatementWithBraces(Statement stmt, TextWriter writer) {
        if (stmt instanceof BlockStmt) {
            stmt.accept(this, writer);
        } else {
            writer.writeString("{");
            writer.writeLine();
            writer.increaseIndent();
            stmt.accept(this, writer);
            writer.decreaseIndent();
            writer.writeLine();
            writer.writeString("}");
            writer.writeLine();
        }
    }


    @Override
    public void visit(final BlockStmt n, TextWriter writer) {

        // Ignore class or interface init block
        if (n.getParentNode().isPresent() && n.getParentNode().get() instanceof ClassOrInterfaceDeclaration) {
            return;
        }

        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });

        writer.writeString("{");
        writer.increaseIndent();
        writer.writeLine();
        n.getStatements().forEach((p) -> {
            p.accept(this, writer);
        });
        writer.decreaseIndent();
        writer.writeLine();
        writer.writeString("}");

    }

    @Override
    public void visit(final BreakStmt n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });

        writer.writeString(BreakKeyword);
        writer.writeString(" ");
        n.getLabel().ifPresent((l) -> {
            l.accept(this, writer);
        });
    }

    @Override
    public void visit(final ContinueStmt n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });

        writer.writeString(ContinueKeyword);
        writer.writeString(" ");
        n.getLabel().ifPresent((l) -> {
            l.accept(this, writer);
        });

    }

    @Override
    public void visit(final DoStmt n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });

        writer.writeString(DoKeyword);
        writer.writeString(" {");
        writer.writeLine();
        writer.increaseIndent();
        n.getBody().accept(this, writer);
        writer.decreaseIndent();
        writer.writeLine();
        writer.writeString("}");

        writer.writeString(WhileKeyword);
        writer.writeString(" ");
        n.getCondition().accept(this, writer);
        writer.writeString(" ");
    }

    @Override
    public void visit(final EmptyStmt n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
    }

    @Override
    public void visit(final ExplicitConstructorInvocationStmt n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getExpression().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getTypeArguments().ifPresent((l) -> {
            writer.writeString("<");
            l.forEach((v) -> {
                v.accept(this, writer);
                writer.writeString(", ");
            });
            writer.removeTrailing(", ");
            writer.writeString(">");
        });
        writer.writeString("(");
        n.getArguments().forEach((p) -> {
            p.accept(this, writer);
            writer.writeString(", ");
        });
        writer.removeTrailing(", ");
        writer.writeString(">");
    }

    @Override
    public void visit(final ExpressionStmt n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        n.getExpression().accept(this, writer);
        writer.writeLine();
    }

    @Override
    public void visit(final ForEachStmt n, TextWriter writer) {
        n.getBody().accept(this, writer);
        n.getIterable().accept(this, writer);
        n.getVariable().accept(this, writer);
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
    }

    @Override
    public void visit(final ForStmt n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });

        writer.writeString(ForKeyword);
        writer.writeString(" ");

        n.getInitialization().forEach((p) -> {
            p.accept(this, writer);
        });
        writer.writeString("; ");
        n.getCompare().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeString("; ");
        n.getUpdate().forEach((p) -> {
            p.accept(this, writer);
        });
        writer.writeString(" ");

        encircleStatementWithBraces(n.getBody(), writer);
    }

    @Override
    public void visit(final IfStmt n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeString(IfKeyword);
        writer.writeString(" ");
        n.getCondition().accept(this, writer);
        writer.writeString(" ");
        encircleStatementWithBraces(n.getThenStmt(), writer);
        n.getElseStmt().ifPresent((l) -> {
            writer.writeString(ElseKeyword);
            encircleStatementWithBraces(l, writer);
        });
    }

    @Override
    public void visit(final LabeledStmt n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });

        n.getLabel().accept(this, writer);
        writer.writeString(": ");
        n.getStatement().accept(this, writer);
    }

    @Override
    public void visit(final ReturnStmt n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeString(ReturnKeyword);
        n.getExpression().ifPresent((l) -> {
            writer.writeString(" ");
            l.accept(this, writer);
        });
    }

    @Override
    public void visit(final SwitchEntry n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });

        n.getLabels().forEach((p) -> {
            writer.writeString(CaseKeyword);
            writer.writeString(" ");
            p.accept(this, writer);
            writer.writeString(":");
            writer.writeLine();
        });
        writer.increaseIndent();
        n.getStatements().forEach((p) -> {
            p.accept(this, writer);
        });
        writer.decreaseIndent();

    }

    @Override
    public void visit(final SwitchStmt n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeString(SwitchKeyword);
        writer.writeString(" ");
        n.getSelector().accept(this, writer);
        writer.writeString(" ");

        writer.writeString("{");
        writer.writeLine();
        writer.increaseIndent();
        n.getEntries().forEach((p) -> {
            p.accept(this, writer);
        });
        writer.decreaseIndent();
        writer.writeLine();
        writer.writeString("}");
    }

    @Override
    public void visit(final SynchronizedStmt n, TextWriter writer) {
        // !!!
//        n.getComment().ifPresent((l) -> {
//            l.accept(this, writer);
//        });
//        n.getBody().accept(this, writer);
//        n.getExpression().accept(this, writer);

    }

    @Override
    public void visit(final ThrowStmt n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeString(ThrowKeyword);
        writer.writeString(" ");
        n.getExpression().accept(this, writer);

    }

    @Override
    public void visit(final TryStmt n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });

        writer.writeString(TryKeyword);
//        // !!!
//        n.getResources().forEach((p) -> {
//            p.accept(this, writer);
//        });
        writer.writeString(" ");
        encircleStatementWithBraces(n.getTryBlock(), writer);

        n.getCatchClauses().forEach((p) -> {
            p.accept(this, writer);
        });
        n.getFinallyBlock().ifPresent((l) -> {
            writer.writeString(" ");
            writer.writeString(FinallyKeyword);
            writer.writeString(" ");
            encircleStatementWithBraces(l, writer);
        });
    }

    @Override
    public void visit(final LocalClassDeclarationStmt n, TextWriter writer) {
        n.getClassDeclaration().accept(this, writer);
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
    }

    @Override
    public void visit(final WhileStmt n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeString(WhileKeyword);
        writer.writeString(" ");
        n.getCondition().accept(this, writer);
        writer.writeString(" ");
        encircleStatementWithBraces(n.getBody(), writer);
    }

    @Override
    public void visit(final UnparsableStmt n, TextWriter writer) {
        n.getComment().ifPresent((l) -> {
            l.accept(this, writer);
        });
        writer.writeString("UnparsableStmt");
    }

//    // Java 8 not support, so ignore it.
//    public void visit(final YieldStmt n, TextWriter writer) { }

}
